using System;
using System.Net.Sockets;
using Org.Mentalis.Network.ProxySocket;
using ProxyServer.Pool;

namespace ProxyServer.ProxyImpl
{
    public class PipedSocket : IDisposable
    {
        private ILogger logger = Logger.GetLogger();

        private readonly Socket from;
        private readonly ProxySocket to;
        private SocketAsyncEventArgs forTo;

        public PipedSocket(Socket from, ProxySocket to)
        {
            this.from = from;
            this.to = to;
        }

        public void StartRelay()
        {
            forTo = BufferContext.GetSocketAsyncEventPool().Pop(true);

            forTo.Completed += ForTo_OnCompleted;

            ReceiveFromRemote();
        }

        private void ReceiveFromRemote()
        {
            try
            {
                if (!to.ReceiveAsync(forTo))
                {
                    ForTo_OnCompleted(this, forTo);
                }
            }
            catch (Exception e)
            {
                logger.Error("error occur when receive data from remote:"+e.Message);
                Dispose();
            }
        }

        private void SendBackToLocal()
        {
            try
            {
                if (!from.SendAsync(forTo))
                {
                    ForTo_OnCompleted(this, forTo);
                }
            }
            catch (Exception e)
            {
                logger.Error("local disconnect unexpectly:" + e.Message);
            }
        }

        void ForTo_OnCompleted(object sender, SocketAsyncEventArgs e)
        {
            switch (e.LastOperation)
            {
                case SocketAsyncOperation.Receive:
                    ProcessReceive(e);
                    break;
                case SocketAsyncOperation.Send:
                    ProcessSend(e);
                    break;
            }
        }

        private void ProcessSend(SocketAsyncEventArgs e)
        {
            if (e.BytesTransferred == 0 || e.SocketError != SocketError.Success)
            {
                to.Shutdown(SocketShutdown.Both);
                to.Close();
                Dispose();
                return;
            }

            e.SetBuffer(e.Offset, BufferContext.BlockSize);

            ReceiveFromRemote();
        }

        private void ProcessReceive(SocketAsyncEventArgs e)
        {
            if (e.BytesTransferred == 0 || e.SocketError != SocketError.Success)
            {
                Dispose();
                return;
            }

            e.SetBuffer(e.Offset, e.BytesTransferred);

            SendBackToLocal();
        }

        public void Dispose()
        {
            if (forTo == null) return;

            forTo.Completed -= ForTo_OnCompleted;
            BufferContext.GetSocketAsyncEventPool().Push(forTo);
        }
    }
}